一. 简介使用Lambdas和Streams
Stream是对集合的包装,通常和lambda一起使用。 使用lambdas可以支持许多操作,如 map, filter, limit, sorted, count, min, max, sum, collect 等等。 同样,Stream使用懒运算,他们并不会真正地读取所有数据,遇到像getFirst() 这样的方法就会结束链式语法。
在接下来的例子中,我们将探索lambdas和streams 能做什么。 我们创建了一个Person类并使用这个类来添加一些数据到list中,将用于进一步流操作。 Person 只是一个简单的POJO类:
public class Person {
private String firstName, lastName, job, gender;
private int salary, age;
public Person(String firstName, String lastName, String job,
String gender, int age, int salary) {
this.firstName = firstName;
this.lastName = lastName;
this.gender = gender;
this.age = age;
this.job = job;
this.salary = salary;
}
// Getter and Setter
// . . . . .
}
接下来,我们将创建两个list,都用来存放Person对象:
List<Person> javaProgrammers = new ArrayList<Person>() {
{
add(new Person("Elsdon", "Jaycob", "Java programmer", "male", 43, 2000));
add(new Person("Tamsen", "Brittany", "Java programmer", "female", 23, 1500));
add(new Person("Floyd", "Donny", "Java programmer", "male", 33, 1800));
add(new Person("Sindy", "Jonie", "Java programmer", "female", 32, 1600));
add(new Person("Vere", "Hervey", "Java programmer", "male", 22, 1200));
add(new Person("Maude", "Jaimie", "Java programmer", "female", 27, 1900));
add(new Person("Shawn", "Randall", "Java programmer", "male", 30, 2300));
add(new Person("Jayden", "Corrina", "Java programmer", "female", 35, 1700));
add(new Person("Palmer", "Dene", "Java programmer", "male", 33, 2000));
add(new Person("Addison", "Pam", "Java programmer", "female", 34, 1300));
}
};
List<Person> phpProgrammers = new ArrayList<Person>() {
{
add(new Person("Jarrod", "Pace", "PHP programmer", "male", 34, 1550));
add(new Person("Clarette", "Cicely", "PHP programmer", "female", 23, 1200));
add(new Person("Victor", "Channing", "PHP programmer", "male", 32, 1600));
add(new Person("Tori", "Sheryl", "PHP programmer", "female", 21, 1000));
add(new Person("Osborne", "Shad", "PHP programmer", "male", 32, 1100));
add(new Person("Rosalind", "Layla", "PHP programmer", "female", 25, 1300));
add(new Person("Fraser", "Hewie", "PHP programmer", "male", 36, 1100));
add(new Person("Quinn", "Tamara", "PHP programmer", "female", 21, 1000));
add(new Person("Alvin", "Lance", "PHP programmer", "male", 38, 1600));
add(new Person("Evonne", "Shari", "PHP programmer", "female", 40, 1800));
}
};
现在我们使用forEach方法来迭代输出上述列表:
System.out.println("所有程序员的姓名:");
javaProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
phpProgrammers.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
我们同样使用forEach方法,增加程序员的工资5%:
System.out.println("给程序员加薪 5% :");
Consumer<Person> giveRaise = e -> e.setSalary(e.getSalary() / 100 * 5 + e.getSalary());
javaProgrammers.forEach(giveRaise);
phpProgrammers.forEach(giveRaise);
二. 过滤器filter()
,让我们显示月薪超过1400美元的PHP程序员:
System.out.println("下面是月薪超过 $1,400 的PHP程序员:")
phpProgrammers.stream()
.filter((p) -> (p.getSalary() > 1400))
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
我们也可以定义过滤器,然后重用它们来执行其他操作:
// 定义 filters
Predicate<Person> ageFilter = (p) -> (p.getAge() > 25);
Predicate<Person> salaryFilter = (p) -> (p.getSalary() > 1400);
Predicate<Person> genderFilter = (p) -> ("female".equals(p.getGender()));
System.out.println("下面是年龄大于 24岁且月薪在$1,400以上的女PHP程序员:");
phpProgrammers.stream()
.filter(ageFilter)
.filter(salaryFilter)
.filter(genderFilter)
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
// 重用filters
System.out.println("年龄大于 24岁的女性 Java programmers:");
javaProgrammers.stream()
.filter(ageFilter)
.filter(genderFilter)
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
三. limit方法
可以限制结果集的个数:
System.out.println("最前面的3个 Java programmers:");
javaProgrammers.stream()
.limit(3)
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
System.out.println("最前面的3个女性 Java programmers:");
javaProgrammers.stream()
.filter(genderFilter)
.limit(3)
.forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));
四. 排序
我们在stream中能处理吗? 答案是肯定的。 在下面的例子中,我们将根据名字和薪水排序Java程序员,放到一个list中,然后显示列表:
System.out.println("根据 name 排序,并显示前5个 Java programmers:");
List<Person> sortedJavaProgrammers = javaProgrammers
.stream()
.sorted((p, p2) -> (p.getFirstName().compareTo(p2.getFirstName())))
.limit(5)
.collect(toList());
sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName()));
System.out.println("根据 salary 排序 Java programmers:");
sortedJavaProgrammers = javaProgrammers
.stream()
.sorted( (p, p2) -> (p.getSalary() - p2.getSalary()) )
.collect( toList() );
sortedJavaProgrammers.forEach((p) -> System.out.printf("%s %s; %n", p.getFirstName(), p.getLastName()));
如果我们只对最低和最高的薪水感兴趣,比排序后选择第一个/最后一个 更快的是min和max方法
System.out.println("工资最低的 Java programmer:");
Person pers = javaProgrammers
.stream()
.min((p1, p2) -> (p1.getSalary() - p2.getSalary()))
.get()
System.out.printf("Name: %s %s; Salary: $%,d.", pers.getFirstName(), pers.getLastName(), pers.getSalary())
System.out.println("工资最高的 Java programmer:");
Person person = javaProgrammers
.stream()
.max((p, p2) -> (p.getSalary() - p2.getSalary()))
.get()
System.out.printf("Name: %s %s; Salary: $%,d.", person.getFirstName(), person.getLastName(), person.getSalary())
五. Collect 对象属性转List, 对象转map
结合 map 方法,我们可以使用 collect 方法来将我们的结果集放到一个字符串,一个 Set 或一个TreeSet中:
System.out.println("将 PHP programmers 的 first name 拼接成字符串:");
String phpDevelopers = phpProgrammers
.stream()
.map(Person::getFirstName)
.collect(joining(" ; ")); // 在进一步的操作中可以作为标记(token)
System.out.println("将 Java programmers 的 first name 存放到 Set:");
Set<String> javaDevFirstName = javaProgrammers
.stream()
.map(Person::getFirstName)
.collect(toSet());
System.out.println("将 Java programmers 的 first name 存放到 TreeSet:");
TreeSet<String> javaDevLastName = javaProgrammers
.stream()
.map(Person::getLastName)
.collect(toCollection(TreeSet::new));
list 转另一种 list
List<String> list1 = Lists.newArrayList();
List<String> list2 = Lists.newArrayList();
list1.add("1");
list1.add("2");
list1.add("3");
list2 = list1.stream().map(string -> {
return "stream().map()处理之后:" + string;
}).collect(Collectors.toList());
list2.stream().forEach(string -> {
System.out.println(string);
});
转Map:
常用方式
代码如下:
public Map<Long, String> getIdNameMap(List<Account> accounts) {
return accounts.stream().collect(Collectors.toMap(Account::getId, Account::getUsername));
}
收集成实体本身map
代码如下:
public Map<Long, Account> getIdAccountMap(List<Account> accounts) {
return accounts.stream().collect(Collectors.toMap(Account::getId, account -> account));
}
account -> account是一个返回本身的lambda表达式,其实还可以使用Function接口中的一个默认方法代替,使整个方法更简洁优雅:
public Map<Long, Account> getIdAccountMap(List<Account> accounts) {
return accounts.stream().collect(Collectors.toMap(Account::getId, Function.identity()));
}
重复key的情况
代码如下:
public Map<String, Account> getNameAccountMap(List<Account> accounts) {
return accounts.stream().collect(Collectors.toMap(Account::getUsername, Function.identity()));
}
这个方法可能报错(java.lang.IllegalStateException: Duplicate key),因为name是有可能重复的。toMap有个重载方法,可以传入一个合并的函数来解决key冲突问题:
public Map<String, Account> getNameAccountMap(List<Account> accounts) {
return accounts.stream().collect(Collectors.toMap(Account::getUsername, Function.identity(), (key1, key2) -> key2));
}
这里只是简单的使用后者覆盖前者来解决key重复问题。
指定具体收集的map
toMap还有另一个重载方法,可以指定一个Map的具体实现,来收集数据:
public Map<String, Account> getNameAccountMap(List<Account> accounts) {
return accounts.stream().collect(Collectors.toMap(Account::getUsername, Function.identity(), (key1, key2) -> key2, LinkedHashMap::new));
}